1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   */
19  package org.codehaus.groovy.control.customizers.builder;
20  
21  import groovy.lang.Closure;
22  import groovy.util.AbstractFactory;
23  import groovy.util.FactoryBuilderSupport;
24  import org.codehaus.groovy.control.customizers.CompilationCustomizer;
25  import org.codehaus.groovy.control.customizers.SourceAwareCustomizer;
26  
27  import java.util.LinkedList;
28  import java.util.List;
29  import java.util.Map;
30  
31  /**
32   * Factory for use with {@link CompilerCustomizationBuilder}. Allows the construction of {@link SourceAwareCustomizer
33   * source aware customizers}. Syntax:
34   * <pre><code>
35   *     // apply CompileStatic AST annotation on .sgroovy files
36   *     builder.source(extension: 'sgroovy') {
37   *         ast(CompileStatic)
38   *     }
39   *
40   *     // apply CompileStatic AST annotation on .sgroovy or .sg files
41   *     builder.source(extensions: ['sgroovy','sg']) {
42   *         ast(CompileStatic)
43   *     }
44   *
45   *     // apply CompileStatic AST annotation on .sgroovy or .sg files
46   *     builder.source(extensionValidator: { it.name in ['sgroovy','sg']}) {
47   *         ast(CompileStatic)
48   *     }
49   *
50   *     // apply CompileStatic AST annotation on files whose name is 'foo'
51   *     builder.source(basename: 'foo') {
52   *         ast(CompileStatic)
53   *     }
54   *
55   *     // apply CompileStatic AST annotation on files whose name is 'foo' or 'bar'
56   *     builder.source(basenames: ['foo', 'bar']) {
57   *         ast(CompileStatic)
58   *     }
59   *
60   *     // apply CompileStatic AST annotation on files whose name is 'foo' or 'bar'
61   *     builder.source(basenameValidator: { it in ['foo', 'bar'] }) {
62   *         ast(CompileStatic)
63   *     }
64   *
65   *     // apply CompileStatic AST annotation on files that do not contain a class named 'Baz'
66   *     builder.source(unitValidator: { unit -> !unit.AST.classes.any { it.name == 'Baz' } }) {
67   *         ast(CompileStatic)
68   *     }
69   *
70   *     // apply CompileStatic AST annotation on class nodes that end with 'CS'
71   *     builder.source(classValidator: { cn -> cn.name.endsWith('CS') }) {
72   *         ast(CompileStatic)
73   *     }
74   * </code></pre>
75   *
76   * @author Cedric Champeau
77   */
78  public class SourceAwareCustomizerFactory extends AbstractFactory implements PostCompletionFactory {
79  
80      public Object newInstance(final FactoryBuilderSupport builder, final Object name, final Object value, final Map attributes) throws InstantiationException, IllegalAccessException {
81          SourceOptions data = new SourceOptions();
82          if (value instanceof CompilationCustomizer) {
83              data.delegate = (CompilationCustomizer) value;
84          }
85          return data;
86      }
87  
88      @Override
89      public void setChild(final FactoryBuilderSupport builder, final Object parent, final Object child) {
90          if (child instanceof CompilationCustomizer && parent instanceof SourceOptions) {
91              ((SourceOptions) parent).delegate = (CompilationCustomizer) child;
92          }
93      }
94  
95      public Object postCompleteNode(final FactoryBuilderSupport factory, final Object parent, final Object node) {
96          SourceOptions data = (SourceOptions) node;
97          SourceAwareCustomizer sourceAwareCustomizer = new SourceAwareCustomizer(data.delegate);
98          if (data.extensionValidator !=null && (data.extension!=null || data.extensions!=null)) {
99              throw new RuntimeException("You must choose between an extension name validator or an explicit extension name");
100         }
101         if (data.basenameValidator!=null && (data.basename!=null || data.basenames!=null)) {
102             throw new RuntimeException("You must choose between an base name validator or an explicit base name");
103         }
104 
105         addExtensionValidator(sourceAwareCustomizer, data);
106         addBasenameValidator(sourceAwareCustomizer, data);
107         if (data.unitValidator!=null) sourceAwareCustomizer.setSourceUnitValidator(data.unitValidator);
108         if (data.classValidator!=null) sourceAwareCustomizer.setClassValidator(data.classValidator);
109         return sourceAwareCustomizer;
110     }
111 
112     private void addExtensionValidator(final SourceAwareCustomizer sourceAwareCustomizer, final SourceOptions data) {
113         final List<String> extensions = data.extensions!=null?data.extensions : new LinkedList<String>();
114         if (data.extension!=null) extensions.add(data.extension);
115         Closure<Boolean> extensionValidator = data.extensionValidator;
116         if (extensionValidator==null && !extensions.isEmpty()) {
117             extensionValidator = new Closure<Boolean>(sourceAwareCustomizer) {
118                 @Override
119                 @SuppressWarnings("unchecked")
120                 public Boolean call(final Object arguments) {
121                     return extensions.contains(arguments);
122                 }
123             };
124         }
125         sourceAwareCustomizer.setExtensionValidator(extensionValidator);
126     }
127 
128     private void addBasenameValidator(final SourceAwareCustomizer sourceAwareCustomizer, final SourceOptions data) {
129         final List<String> basenames = data.basenames!=null?data.basenames : new LinkedList<String>();
130         if (data.basename!=null) basenames.add(data.basename);
131         Closure<Boolean> basenameValidator = data.basenameValidator;
132         if (basenameValidator==null && !basenames.isEmpty()) {
133             basenameValidator = new Closure<Boolean>(sourceAwareCustomizer) {
134                 @Override
135                 @SuppressWarnings("unchecked")
136                 public Boolean call(final Object arguments) {
137                     return basenames.contains(arguments);
138                 }
139             };
140         }
141         sourceAwareCustomizer.setBaseNameValidator(basenameValidator);
142     }
143 
144     public static class SourceOptions {
145         public CompilationCustomizer delegate;
146         // validate with closures
147         public Closure<Boolean> extensionValidator;
148         public Closure<Boolean> unitValidator;
149         public Closure<Boolean> basenameValidator;
150         public Closure<Boolean> classValidator;
151 
152         // validate with one string
153         public String extension;
154         public String basename;
155 
156         // validate with list of strings
157         public List<String> extensions;
158         public List<String> basenames;
159     }
160 }